package com.lagou.rpc.provider.handler;

import com.alibaba.fastjson.JSON;
import com.lagou.rpc.common.RpcRequest;
import com.lagou.rpc.common.RpcResponse;
import com.lagou.rpc.provider.anno.RpcService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.springframework.beans.BeansException;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 *   服务端业务处理类
 *   业务需求：
 *   1.将标有 @RpcService注解的bean缓存   实现接口：ApplicationContextAware ，重写方法setApplicationContext
 *   2.接收客户端的请求
 *   3.根据传来的beanname，从缓存中找到对应的Bean
 *   4.解析请求中的方法名称，参数类型，参数信息
 *   5.反射，调用Bean中的方法
 *   6.给客户端进行响应
 *   继承消息入站的类，泛型是string，因为服务端和客户端通过json字符串传递信息
 */
@Component
@ChannelHandler.Sharable
public class RpcServerHandler extends SimpleChannelInboundHandler<String> implements ApplicationContextAware {


    //  定义一个缓存
    private static final Map SERVICE_INSTANCE_MAP = new ConcurrentHashMap();


    /**
     *  可以从该方法中找到标有@RpcService注解的bean
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 从容器中获取加了@RpcService注解的bean
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(RpcService.class);
        if (serviceMap != null && serviceMap.size() > 0){ // 判断集合是否为空
            //  集合不为空，就遍历集合
            Set<Map.Entry<String, Object>> entries = serviceMap.entrySet();
            for (Map.Entry<String, Object> item : entries) {
                Object serviceBean = item.getValue();  //  UserServiceImpl
                //  判断获取的类有没有实现接口  为了严谨
                if (serviceBean.getClass().getInterfaces().length == 0){
                    throw new RuntimeException("服务必须实现接口");
                }
                // 默认取serviceBean实现的第一个接口，作为缓存bean的名称
                String name = serviceBean.getClass().getInterfaces()[0].getName();
                SERVICE_INSTANCE_MAP.put(name,serviceBean);
            }
        }
    }


    /**
     *  通道读取就绪事件
     * @param channelHandlerContext
     * @param msg   所读取到的消息
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        // 1. 接受客户端的请求  msg是json的字符串，转换成RpcRequst对象
        RpcRequest rpcRequest = JSON.parseObject(msg, RpcRequest.class);
        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(rpcRequest.getRequestId());
        try {
           // 返回给客户端的结果      调用本类中的业务处理逻辑方法
            rpcResponse.setResult(handler(rpcRequest));
        }catch (Exception e){
            e.printStackTrace();
            rpcResponse.setError(e.getMessage());
        }

        // 6.给客户端进行响应                    转换成json字符串
        channelHandlerContext.writeAndFlush(JSON.toJSONString(rpcResponse));


    }

    /**
     *  业务处理逻辑
     * @return
     */
    public Object handler(RpcRequest rpcRequest) throws InvocationTargetException {
//        3.根据传来的beanname，从缓存中找到对应的Bean
        Object serviceBean = SERVICE_INSTANCE_MAP.get(rpcRequest.getClassName());
        if (serviceBean == null){
            throw new RuntimeException("根据beanName找不到服务，beanName："+rpcRequest.getClassName());
        }
//        4.解析请求中的方法名称，参数类型，参数信息
        Class<?> serviceBeanClass = serviceBean.getClass();
        String methodName = rpcRequest.getMethodName(); // 获取方法名称
        Class<?>[] parameterTypes = rpcRequest.getParameterTypes(); // 获取参数类型
        Object[] parameters = rpcRequest.getParameters(); // 获取方法中的参数
//        5.反射，调用Bean中的方法，使用cglib
        FastClass fastClass = FastClass.create(serviceBeanClass); // cglib中的方法
        FastMethod method = fastClass.getMethod(methodName, parameterTypes);
        //                  方法所在的类，从缓存中获取的
        return method.invoke(serviceBean,parameters);
    }


}
